using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using T.FAS.Runtime.Metadata.Entity;
using T.FAS.Runtime.Action.Dao;
using T.FAS.Runtime.Action.SPI;
using T.FAS.Runtime.Base.Entity;
using T.FAS.Runtime.DataAccess.Service;
using T.FAS.Runtime.Metadata.Entity.DataModel;
using T.FAS.Runtime.Metadata.Client;
//using T.FAS.Authority.Metadata.Entity;
//using T.FAS.Authority.MetaData.Dev.Plugin;
using DataType = T.FAS.Runtime.Base.Entity.DataType;

namespace T.FAS.Runtime.Action.Engine
{
    public class SQLActionEngine : IActionExecuteProxy, IActionDataResultBuilder, IActionExecuteHandler
    {
        //private readonly IDataPermissionsService permissionsService = DataPermissionsService.GetInstance();
        //private readonly ActionPermissionDefinitionManager permissionManager = new ActionPermissionDefinitionManager();
        public void AfterExecute(SPI.ActionExecContext execContext)
        {
            
        }

        public void BeforeExecute(SPI.ActionExecContext execContext)
        {
            
        }

        public TResult BuildActionDataResult(SPI.ActionExecContext context, object dataResult)
        {
            var actionMetaData = ActionRTPUtil.ActionMetadataValid(context.MetaData);
            if (actionMetaData.ReturnValueType == Metadata.Entity.ReturnValueType.Void)
                return null;
            if (actionMetaData.Method == null)
                throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError, string.Format(CommonStringClassEngine.ActionExec_MissingMethodDefinition, actionMetaData.Code));
            var outputResult = actionMetaData.MethodParams.Find(x => x.ParamType == ParamType.Out);
            if (outputResult == null)
                throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError,string.Format(CommonStringClassEngine.ActionExec_MissingMethodRetuanValueTypeDefinition, actionMetaData.Code));

            var dsResult = dataResult as DataSet;
            if (dsResult == null || dsResult.Tables == null || dsResult.Tables.Count <= 0)
                throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError,string.Format(CommonStringClassEngine.SQLActionEngine_BuildActionDataResult2,actionMetaData.Code));
            var dtResult = dsResult.Tables[0];
            if (outputResult.ParamSourceType == ParamSourceType.Basic)
            {
                var tResult = new TCommonResult<TRecord>()
                {
                    ActionCode = actionMetaData.Code,
                    ActionName = actionMetaData.Name,
                    Count = 0,
                    ResultType = actionMetaData.ReturnValueType == ReturnValueType.Record ? DataType.Record : DataType.List,
                    DataContent = dtResult,
                };
                tResult.Schema = BasicTypeData.BuildSchema(outputResult.ParamValueType);
                var isUserDefineResult = UserDefineResultHandler.ConvertToTResultByMappingJS(dataResult, context, tResult);
                if (!isUserDefineResult)
                {
                    if (tResult.ResultType == DataType.Record)
                    {
                        object executeResult = "";
                        if (dtResult != null && dtResult.Rows != null && dtResult.Rows.Count > 0)
                        {
                            executeResult = dtResult.Rows[0][0];
                        }
                        tResult.Record = new TRecord();
                        tResult.Record.SetValue(BasicTypeData.DefaultPropertyName, executeResult);
                    }
                    else
                    {
                        if (dtResult == null || dtResult.Rows == null || dtResult.Rows.Count <= 0)
                        {
                            tResult.List = new TArray<TRecord>(new TEntity());
                        }
                        else
                        {
                            foreach (DataRow row in dtResult.Rows)
                            {
                                if (tResult.List == null)
                                {
                                    tResult.Schema = BasicTypeData.BuildSchema(outputResult.ParamValueType);
                                    tResult.List = new TArray<TRecord>(new TEntity());
                                }
                                tResult.List.Add(new TRecord(new Dictionary<string, object>() { { BasicTypeData.DefaultPropertyName, row[0] } }));
                            }
                        }
                        tResult.Record = new TRecord();
                    }
                }
                return tResult;
            }
            else
            {
                var mappingResult = new TCommonResult<TRecord>();
                if (!string.IsNullOrEmpty(actionMetaData.ResultMappingJS))
                {
                    mappingResult = new TCommonResult<TRecord>()
                    {
                        ActionCode = actionMetaData.Code,
                        ActionName = actionMetaData.Name,
                        Count = 0,
                        ResultType = actionMetaData.ReturnValueType == ReturnValueType.Record ? DataType.Record : DataType.List,
                        DataContent = dataResult,
                    };
                }
                if (outputResult.ParamSourceType == ParamSourceType.EO)
                {
                    var eoMetaData = MetaDataClientService.GetInstance().GetMetaDataContent(outputResult.SourceID) as EntityObject;
                    if (eoMetaData == null)
                        throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError, string.Format(CommonStringClassEngine.SQLActionEngine_CannotFindReturnEOEntity,actionMetaData.Code,outputResult.SourceID));
                    dtResult.TableName = outputResult.SourceName;
                    var eoSchema = eoMetaData.BuildSchema();
                    eoSchema.ResultType = actionMetaData.ReturnValueType == ReturnValueType.Record ? DataType.Record : DataType.List;
                    return BuildTResult(dtResult, eoSchema, context, mappingResult);
                }
                else if (outputResult.ParamSourceType == ParamSourceType.DO || outputResult.ParamSourceType == ParamSourceType.SDO)
                {
                    var doMetaData = MetaDataClientService.GetInstance().GetMetaDataContent(outputResult.SourceID) as Metadata.Entity.DataModel.DataObject;
                    if (doMetaData == null)
                        throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError, string.Format(CommonStringClassEngine.SQLActionEngine_CannotFindReturnEOEntity, actionMetaData.Code,outputResult.SourceID));
                    dtResult.TableName = outputResult.SourceName;
                    var doSchema = doMetaData.BuildSchema();
                    doSchema.ResultType = actionMetaData.ReturnValueType == ReturnValueType.Record ? DataType.Record : DataType.List;
                    return BuildTResult(dtResult, doSchema, context, mappingResult);
                }
                else if (outputResult.ParamSourceType == ParamSourceType.DQ)
                {
                    var dqMetaData = MetaDataClientService.GetInstance().GetMetaDataContent(outputResult.SourceID) as Metadata.Entity.DataModel.DataQuery;
                    if (dqMetaData == null)
                        throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError, string.Format(CommonStringClassEngine.SQLActionEngine_CannotFindReturnEOEntity, actionMetaData.Code,outputResult.SourceID));
                    dtResult.TableName = outputResult.SourceName;
                    var dqSchema = dqMetaData.BuildSchema();
                    dqSchema.ResultType = actionMetaData.ReturnValueType == ReturnValueType.Record ? DataType.Record : DataType.List;
                    return BuildTResult(dtResult,dqSchema,context, mappingResult);
                }
                return null;
            }
        }

        private TResult BuildTResult(object result,TSchema schema, ActionExecContext context, TCommonResult<TRecord> mappingResult)
        {
            mappingResult.Schema = schema;
            var isUserDefineResult = UserDefineResultHandler.ConvertToTResultByMappingJS(result, context, mappingResult);
            if (isUserDefineResult)
                return mappingResult;
            return TResultBuilder.BuildTResult(result, schema);
        }

        public object Execute(SPI.ActionExecContext context)
        {
            ActionParamMappingHandler.BuildParamsByMapping(context);

            var command = BuildFasDBCommand(context);
            
            UpdateAuthSql(context,command);
            
            var result = UnifiedDataAccessServiceFactory.GetInstance().Query(command);

            return result;
        }
        public void ExecuteError(SPI.ActionExecContext execContext, Exception exception)
        {
            
        }

        private FasDBCommand BuildFasDBCommand(SPI.ActionExecContext context)
        {
            var action = ActionRTPUtil.ActionMetadataValid(context.MetaData);
            var fasParamList = new List<FasParam>();
            if (action.MethodParams != null && action.MethodParams.Count > 0)
            {
                var inParamsAndVariables = action.MethodParams.FindAll(x => x.ParamType == Metadata.Entity.ParamType.In || x.ParamType == ParamType.Variable);
                if (inParamsAndVariables != null && inParamsAndVariables.Count > 0)
                {
                    inParamsAndVariables.ForEach(p =>
                    {
                        if (!context.DataContent.ContainsKey(p.Code) && p.DefaultValue == null)
                        {
                            throw new FasException(ActionExecuteExceptionCode.ActionExecuteParamNullError, (p.ParamType == ParamType.In) ? CommonStringClassEngine.SQLActionEngine_BuildFasDBCommand : CommonStringClassEngine.SQLActionEngine_BuildFasDBCommand1 + string.Format(CommonStringClassEngine.ActionExec_ParameterIsNotAssigned, p.Code));
                        }
                        if (context.DataContent.ContainsKey(p.Code))
                        {
                            var param = new FasParam();
                            param.Code = p.Code;
                            param.Name = p.Name;
                            param.ParamOrder = p.SortOrder;
                            param.DataType = p.SysDataType;
                            param.Value = context.DataContent[p.Code];
                            fasParamList.Add(param);
                        }

                    });
                }
            }
            var dbInfo = ActionEngineLocalCache.GetInstance().GetDataBaseInfoFromLocalCache(action.RelatedMetaDataID);
            if (dbInfo == null)
            {
                dbInfo = new DataBaseInfoDao().GetDefaultDataBaseInfoById(action.RelatedMetaDataID);
                if(dbInfo!=null)
                    ActionEngineLocalCache.GetInstance().AddDataBaseInfoCache(dbInfo);
            }
            if (dbInfo == null)
            {
                throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError, string.Format(CommonStringClassEngine.SQLActionEngine_BuildFasDBCommand3,action.RelatedMetaDataID));
            }
            else
            {
                ActionEngineLocalCache.GetInstance().AddDataBaseInfoCache(dbInfo);
            }
            var command = new FasDBCommand();
            command.ConnectionKey = dbInfo.ConnectionKey;
            command.DatabaseType = dbInfo.DBType;
            command.SqlStatement = action.Method.MethodContent;
            command.FasParams = fasParamList;
            AppendFasDBCommandForAdvanceSQLAction(context,command,action);
            return command;
        }
        private void AppendFasDBCommandForAdvanceSQLAction(SPI.ActionExecContext context,FasDBCommand command,ActionMetaData action)
        {
            if (action == null || action.ActionType != Metadata.Entity.ActionType.SQL ||
                (action.ActionType == Metadata.Entity.ActionType.SQL && !action.Method.IsAdvanced))
                return;
            if (string.IsNullOrEmpty(action.Method.MethodContent))
            {
                command.SqlStatement = command.SqlStatement.Replace("@queryFilter", " 1=1");
                return;
            }
            var content = JsonConvert.DeserializeObject<SQLActionContent>(action.Method.MethodContent);
            if (content == null || content.DynamicParams == null)
            {
                if (content != null)
                {
                    command.SqlStatement = content.SQLContent.Replace("@queryFilter", " 1=1");
                }
                return;
            }
            var allDynamicQueryFilterParams = content.DynamicParams.FindAll(p => p.IsQueryFilter);
            if (allDynamicQueryFilterParams == null || allDynamicQueryFilterParams.Count == 0)
            {
                command.SqlStatement = content.SQLContent.Replace("@queryFilter", " 1=1");
                return;
            }
            var maxSortOrder = (action.MethodParams == null || action.MethodParams.Count<=0)?0:action.MethodParams.OrderBy(p => p.SortOrder).Last().SortOrder;
            var advanceQueryFilter =new StringBuilder();
            advanceQueryFilter.Append(" 1=1 ");
            allDynamicQueryFilterParams.ForEach(p =>
            {
                if (context.DataContent.ContainsKey(p.Code))
                {
                    maxSortOrder++;
                    advanceQueryFilter.Append($" and {p.MapField} = @{p.Code}");
                    command.FasParams.Add(new FasParam()
                    {
                        Code = p.Code,
                        Name = p.Name,
                        ParamOrder = maxSortOrder,
                        DataType = p.SysDataType,
                        Value = context.DataContent[p.Code],
                    });
                }
            });
            command.SqlStatement = content.SQLContent.Replace("@queryFilter", advanceQueryFilter.ToString());
        }

        public object ExecuteWithSubEngine(IActionExecuteEngine subEngine, ActionExecContext context)
        {
            throw new NotImplementedException();
        }

        #region 权限相关

        private void UpdateAuthSql(ActionExecContext context, FasDBCommand command)
        {
            var action = context.MetaData as ActionMetaData;
            var authSqls = new List<string>();
            if (action.IsPermissionControl)
            {
                // ToDo
                //var permissionData = GetAuthRuntimeParams(action);
                //foreach (var permission in permissionData)
                //{
                //    var sql = permissionsService.GetFilterSqlByUserId(Base.Common.UserUtil.GetCurrentUserID(), permission.FunctionId);
                //    authSqls.Add(sql);
                //}
            }
            if (authSqls.Count > 0)
            {
                authSqls = ProcessAuthSqls(command, authSqls);
                StringBuilder sb = new StringBuilder();
                if (authSqls != null && authSqls.Count > 0)
                {
                    for (int i = 0; i < authSqls.Count; i++)
                    {
                        sb.Append("(");
                        sb.Append(authSqls[i]);
                        sb.Append(")");
                        if (i < authSqls.Count - 1)
                            sb.Append(" and ");
                    }
                }
                command.SqlStatement = command.SqlStatement.Replace("@"+ActionMetaDataConst.ActionMetaDataVariableForAuth, sb.ToString());
            }
        }
        /// <summary>
        /// 处理权限SQL
        /// </summary>
        /// <param name="dq"></param>
        /// <param name="authSQLs"></param>
        /// <returns></returns>
        private List<string> ProcessAuthSqls(FasDBCommand command, List<string> authSQLs)
        {
            if (authSQLs == null || authSQLs.Count == 0) return authSQLs;
            if (command == null) return authSQLs;
            DatabaseType databaseType= command.DatabaseType;
            
            List<string> result = new List<string>();
            foreach (var sql in authSQLs)
            {
                if (databaseType == DatabaseType.MySQL)
                {
                    result.Add(sql.ToLower().Replace("with(nolock)", ""));
                }
                else
                {
                    result.Add(sql);
                }
            }
            return result;
        }
        // Todo
        //private List<AuthRuntimeParam> GetAuthRuntimeParams(ActionMetaData action)
        //{
        //    try
        //    {
        //        return permissionManager.GetRuntimeParam(action.ID);
        //    }
        //    catch (Exception e)
        //    {

        //        throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError, string.Format(CommonStringClassEngine.SQLActionEngine_GetAuthRuntimeParams,action.ID,action.Code), e);
        //    }
        //}
        #endregion
    }
}
